#include <stdio.h>
#include <iostream>
#include "buf.h"
#include <error.h>

// This file contains some rudimentary tests to help you test
// your buffer manager.
//
// Please note that when grading, we will use additional tests not included in this file!
//


#define CALL(c)    { Status s; \
                     if ((s = c) != OK) { \
               cerr << "At " << __FILE__ << ":" << __LINE__ << endl << "  "; \
                       Error::print(s); \
                       exit(1); \
                     } \
                   }

#define ASSERT(c)  { if (!(c)) { \
               cerr << "At " << __FILE__ << ":" << __LINE__ << endl << "  "; \
                       cerr << "This condition should hold: " #c << endl; \
                       exit(1); \
             } \
                   }

#define FAIL(c)  { Status s; \
                   if ((s = c) == OK) { \
             cerr << "At " << __FILE__ << ":" << __LINE__ << endl << "  "; \
                     cerr << "This call should fail: " #c << endl; \
                     exit(1); \
             } \
                   else \
                   { \
                     cout << "Failing as expected while executing: " #c << endl; \
                   } \
             }

BufMgr*     bufMgr;

int main()
{
    DB          db;
    File*    file1;
    File*    file2;
    File*     file3;
    File*       file4;
    int        i;
    const int   num = 100;
    int         j[num];    

    // create buffer manager

    bufMgr = new BufMgr(num);

    // create dummy files

    (void)db.destroyFile("test.1");
    (void)db.destroyFile("test.2");
    (void)db.destroyFile("test.3");
    (void)db.destroyFile("test.4");

    CALL(db.createFile("test.1"));
    ASSERT(db.createFile("test.1") == FILEEXISTS);
    CALL(db.createFile("test.2"));
    CALL(db.createFile("test.3"));
    CALL(db.createFile("test.4"));

    CALL(db.openFile("test.1", file1));
    CALL(db.openFile("test.2", file2));
    CALL(db.openFile("test.3", file3));
    CALL(db.openFile("test.4", file4));

    // test buffer manager

    Page* page;
    Page* page2;
    Page* page3;
    Page cmp;
    int pageno, pageno2, pageno3;

    cout << "Allocating pages in a file..." << endl;
    for (i = 0; i < num; i++) {
      CALL(bufMgr->allocPage(file1, j[i], page));
      sprintf((char*)page, "test.1 Page %d %7.1f", j[i], (float)j[i]);
      CALL(bufMgr->unPinPage(file1, j[i], true));
    }

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

    cout << "Reading pages back..." << endl;
    for (i = 0; i < num; i++) {
      CALL(bufMgr->readPage(file1, j[i], page));
      sprintf((char*)&cmp, "test.1 Page %d %7.1f", j[i], (float)j[i]);
      ASSERT(memcmp(page, &cmp, strlen((char*)&cmp)) == 0);
      CALL(bufMgr->unPinPage(file1, j[i], false));
    }

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

    cout << "Writing and reading back multiple files..." << endl;
	for (i = 0; i < num/3; i++) {
      CALL(bufMgr->allocPage(file2, pageno2, page2));
      sprintf((char*)page2, "test.2 Page %d %7.1f", pageno2, (float)pageno2);
      CALL(bufMgr->allocPage(file3, pageno3, page3));
      sprintf((char*)page3, "test.3 Page %d %7.1f", pageno3, (float)pageno3);

      if ( i == 0)
    {
      cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    }

      pageno = j[random() % num];
      CALL(bufMgr->readPage(file1, pageno, page));
      sprintf((char*)&cmp, "test.1 Page %d %7.1f", pageno, (float)pageno);
      ASSERT(memcmp(page, &cmp, strlen((char*)&cmp)) == 0);
      cout << (char*)page << endl;

      CALL(bufMgr->readPage(file2, pageno2, page2));
      sprintf((char*)&cmp, "test.2 Page %d %7.1f", pageno2, (float)pageno2);
      ASSERT(memcmp(page2, &cmp, strlen((char*)&cmp)) == 0);
      CALL(bufMgr->readPage(file3, pageno3, page3));
      sprintf((char*)&cmp, "test.3 Page %d %7.1f", pageno3, (float)pageno3);
      ASSERT(memcmp(page3, &cmp, strlen((char*)&cmp)) == 0);

      CALL(bufMgr->unPinPage(file1, pageno, true));
	}

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

    for (i = 0; i < num/3; i++) {
      CALL(bufMgr->unPinPage(file2, i+1, true));
      CALL(bufMgr->unPinPage(file2, i+1, true));
      CALL(bufMgr->unPinPage(file3, i+1, true));
      CALL(bufMgr->unPinPage(file3, i+1, true));
    }

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

#ifdef DEBUGBUF
    bufMgr->printSelf();
#endif 

    cout << "\nReading \"test.1\"...\n";
    for (i = 1; i < num/3; i++) {
      CALL(bufMgr->readPage(file1, i, page2));
      sprintf((char*)&cmp, "test.1 Page %d %7.1f", i, (float)i);
      ASSERT(memcmp(page2, &cmp, strlen((char*)&cmp)) == 0);
      CALL(bufMgr->unPinPage(file1, i, false));
    }

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

    cout << "\nReading \"test.2\"...\n";
    for (i = 1; i < num/3; i++) {
      CALL(bufMgr->readPage(file2, i, page2));
      sprintf((char*)&cmp, "test.2 Page %d %7.1f", i, (float)i);
      ASSERT(memcmp(page2, &cmp, strlen((char*)&cmp)) == 0);
      cout << (char*)page2 << endl;
      CALL(bufMgr->unPinPage(file2, i, false));
    }

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

    cout << "\nReading \"test.3\"...\n";
    for (i = 1; i < num/3; i++) {
      CALL(bufMgr->readPage(file3, i, page3));
      sprintf((char*)&cmp, "test.3 Page %d %7.1f", i, (float)i);
      ASSERT(memcmp(page3, &cmp, strlen((char*)&cmp)) == 0);
      cout << (char*)page3 << endl;
      CALL(bufMgr->unPinPage(file3, i, false));
    }

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

    cout << "\nTesting error condition...\n\n";

    Status status;
    FAIL(status = bufMgr->readPage(file4, 1, page));
    Error::print(status);
    CALL(bufMgr->allocPage(file4, i, page));
    CALL(bufMgr->unPinPage(file4, i, true));
    FAIL(status = bufMgr->unPinPage(file4, i, false));
    Error::print(status);

    for (i = 0; i < num; i++) {
      CALL(bufMgr->allocPage(file4, j[i], page));
      sprintf((char*)page, "test.4 Page %d %7.1f", j[i], (float)j[i]);
     
    }
    int tmp;
    FAIL(status = bufMgr->allocPage(file4, tmp, page));
    Error::print(status);

#ifdef DEBUGBUF
    bufMgr->printSelf();
#endif 

    for (i = 0; i < num; i++)
    {
      CALL(bufMgr->unPinPage(file4, i+2, true));
      
    }
    
    cout << "\nReading \"test.1\"...\n";
    for (i = 1; i < num; i++) {
      CALL(bufMgr->readPage(file1, i, page));
      sprintf((char*)&cmp, "test.1 Page %d %7.1f", i, (float)i);
      ASSERT(memcmp(page, &cmp, strlen((char*)&cmp)) == 0);
      
      if ( i < num/2 )
      {
             sprintf((char*)page, "test.1 Page %d %7.1f", i, (float)i);
             CALL(bufMgr->unPinPage(file1, i, true)); // make the page dirty
             CALL(bufMgr->readPage(file1, i, page));  // make the page pinned 
      }
      cout << (char*)page << endl;
    }
    
    FAIL(status = bufMgr->flushFile(file1));
    Error::print(status);

    for (i = 1; i < num; i++) 
      CALL(bufMgr->unPinPage(file1, i, true));

    CALL(bufMgr->flushFile(file1));

	/* Begin Mac's test cases */
	(void)db.destroyFile("mactest1");
    (void)db.destroyFile("mactest2");

	//ALLOCBUF
	//*NOTE: allocBuf is private, so had to move it to public to do this testing
    //Currently all files are flushed
	//Try to allocate many times in a row
	unsigned int loc_frame;
	//cout << endl << "Stress testing AllocBuf\n";
	//for(i = 0; i < 100000; i++)
	//	CALL(bufMgr->allocBuf(loc_frame));

	//Now fill buffer up, make all pages dirty, and then allocBuf, so it writes the page out
	//cout << endl << "AllocBuf: testing write back dirty page\n";
	//first write 100 pages to the buffer
	int loc1[num];
	for(i = 0; i < num; i++)
	{
		CALL(bufMgr->allocPage(file1, loc1[i], page));
	}
	//First try to write an additional page to induce BUFFEREXCEDED error
	//FAIL(status = bufMgr->allocBuf(loc_frame));
	//Error::print(status);
	//now they are all pinned
	for(i = 0; i < num; i++)
	{
		CALL(bufMgr->unPinPage(file1, loc1[i], false));
	}
	//now call alocBuf to see if it writes page back, AND sets the refbit
	//CALL(bufMgr->allocBuf(loc_frame)); //Testing writeback etc... when buffer is full but there are
	//some unpinned

	//END ALLOCBUF

	//READPAGE
	//page doesn't exist, should return BUFMAPNOTFOUND
	File *file_loc;
	CALL(db.createFile("mactest1"));
    CALL(db.openFile("mactest1", file_loc));
	Page *page_loc;
	int loc2[1000000];
	FAIL(status = bufMgr->readPage(file_loc, loc1[1], page));
	Error::print(status);

	//now allocate 100 pages and read them all, then try to allocate a final 1 to get 
	//a BUFFEREXCEDED error
	for(i = 0; i < 100; i++)
	{
		CALL(bufMgr->allocPage(file_loc, loc2[i], page_loc));
		CALL(bufMgr->unPinPage(file_loc, loc2[i], false));
		
	}
	File *file_loc2;
	CALL(db.createFile("mactest2"));
    CALL(db.openFile("mactest2", file_loc2));
	Page *page_loc2;
	int loc3[100];
	for(i = 0; i < 100; i++)
	{
		CALL(bufMgr->allocPage(file_loc2, loc3[i], page_loc2));
	}
	//finally try to read one from file_loc
	FAIL(status = bufMgr->readPage(file_loc, loc2[0], page_loc));
	Error::print(status);
	i++;
	FAIL(status = bufMgr->allocPage(file_loc, loc2[i], page_loc));
	Error::print(status);

	//now stress test
	for(i = 0; i < 100; i++)
	{
		CALL(bufMgr->unPinPage(file_loc2, loc3[i], false));
	}
	FAIL(status = bufMgr->unPinPage(file_loc2, loc3[1], false)); //PAGE NOT PINNED
	Error::print(status);
	for(int j = 0; j < 100; j++)
	{
		for(i = 0; i < 100; i++)
		{
			CALL(bufMgr->readPage(file_loc, loc2[i], page_loc));
		}
	}

	//END READPAGE

	//UNPINPAGE
	  //buffermapnotfound, from previous we know mactest2 none are in there
	FAIL(status = bufMgr->unPinPage(file_loc2, loc3[0], false));
	Error::print(status);
	for(int m = 0; m < 100; m++)
	{
	for(i = 0; i < 100; i++)
		CALL(bufMgr->unPinPage(file_loc, loc2[i], false));
	}
	FAIL(status = bufMgr->unPinPage(file_loc, loc2[0], false));
	Error::print(status);
	//END UNPINPAGE

	//ALLOCPAGE
	  //tested BufferExceeded error earlier by ReadPage
	//stress test AllocPage
	for(i = 200; i < 1000; i++)
	{
		CALL(bufMgr->allocPage(file_loc, loc2[i], page_loc));
		CALL(bufMgr->unPinPage(file_loc, loc2[i], false));
	}
	//END ALLOCPAGE

	//DISPOSEPAGE
	  //PAGEPINNED
	CALL(bufMgr->allocPage(file_loc, loc2[200], page_loc));
	FAIL(status = bufMgr->disposePage(file_loc, loc2[200]));
	Error::print(status);
	  //BadPageNo
	FAIL(status = bufMgr->disposePage(file_loc, 0));
	Error::print(status);
	//END DISPOSEPAGE

	CALL(bufMgr->unPinPage(file_loc, loc2[200], page_loc));

	CALL(db.closeFile(file_loc));
    CALL(db.destroyFile("mactest1"));
	CALL(db.closeFile(file_loc2));
    CALL(db.destroyFile("mactest2"));
		//CONSISTENCY TESTS
	(void)db.destroyFile("contests");
	File *contest_file;
	Page *contest_page;
	int contest[1000];
	db.createFile("contests");
    CALL(db.openFile("contests", contest_file));
		/*
		DirtyBit Consistency Test
		we can allocPage, 
		actually write something to the page, then unPinPage and get 
		the clock algorithm to remove the page from the buffer. 
		Then we readPage and check if what we wrote to the page is correct,
		which would indicate "dirty" was set and consistent throughout the page's life.
		*/
	CALL(bufMgr->allocPage(contest_file, contest[0], contest_page));
	sprintf((char*)contest_page,"DirtyBit Consistency Test");
    sprintf((char*)&cmp, "DirtyBit Consistency Test");
	CALL(bufMgr->unPinPage(contest_file, contest[0], true));
	for(i = 0; i < num; i++)
		CALL(bufMgr->allocPage(contest_file, contest[i + 1], contest_page));
	for(i = 0; i < num; i++)
		CALL(bufMgr->unPinPage(contest_file, contest[i + 1], false));
	CALL(bufMgr->readPage(contest_file, contest[0], contest_page));
    ASSERT(memcmp(contest_page, &cmp, strlen((char*)&cmp)) == 0);

	//FlushFile DirtyBit, try the same thing as above but call FlushFile to check
	//if flushfile is writing dirty pages
	sprintf((char*)contest_page,"DirtyBit FlushFile Consistency Test");
    sprintf((char*)&cmp, "DirtyBit FlushFile Consistency Test");
	CALL(bufMgr->unPinPage(contest_file, contest[0], true));
	CALL(bufMgr->flushFile(contest_file));
	CALL(bufMgr->readPage(contest_file, contest[0], contest_page));
    ASSERT(memcmp(contest_page, &cmp, strlen((char*)&cmp)) == 0);
	CALL(bufMgr->unPinPage(contest_file, contest[0], contest_page));
	for(i = 0; i < num; i++)
		CALL(bufMgr->allocPage(contest_file, contest[i + 1], contest_page));
	for(i = 0; i < num; i++){
		CALL(bufMgr->unPinPage(contest_file, contest[i + 1], false));
		CALL(bufMgr->disposePage(contest_file, contest[i + 1]));
	}
	CALL(bufMgr->readPage(contest_file, contest[0], contest_page));

	//DirtyBit NotSet Consistency Test
	sprintf((char*)contest_page,"Do not write this to disk");
	CALL(bufMgr->unPinPage(contest_file, contest[0], false));
	for(i = 0; i < num; i++)
		CALL(bufMgr->allocPage(contest_file, contest[i + 1], contest_page)); //Boot target page
	for(i = 0; i < num; i++)
		CALL(bufMgr->unPinPage(contest_file, contest[i + 1], false));
	CALL(bufMgr->readPage(contest_file, contest[0], contest_page));
    ASSERT(memcmp(contest_page, &cmp, strlen((char*)&cmp)) == 0); 
	CALL(bufMgr->unPinPage(contest_file, contest[0], false));
	for(i = 0; i < num; i++)
		CALL(bufMgr->allocPage(contest_file, contest[i + 1], contest_page));
	for(i = 0; i < num; i++){
		CALL(bufMgr->unPinPage(contest_file, contest[i + 1], false));
		CALL(bufMgr->disposePage(contest_file, contest[i + 1]));
	}

	//DirtyBit NotSet Flushfile Consistency Test
	CALL(bufMgr->readPage(contest_file, contest[0], contest_page));
	sprintf((char*)contest_page,"Do not write this to disk");
	CALL(bufMgr->unPinPage(contest_file, contest[0], false));
	CALL(bufMgr->flushFile(contest_file));
	for(i = 0; i < num; i++)
		CALL(bufMgr->allocPage(contest_file, contest[i + 1], contest_page));
	for(i = 0; i < num; i++){
		CALL(bufMgr->unPinPage(contest_file, contest[i + 1], false));
		CALL(bufMgr->disposePage(contest_file, contest[i + 1]));
	}
	CALL(bufMgr->readPage(contest_file, contest[0], contest_page));
    ASSERT(memcmp(contest_page, &cmp, strlen((char*)&cmp)) == 0);
	CALL(bufMgr->unPinPage(contest_file, contest[0], false));

	//Read a disposed page
	
	CALL(bufMgr->allocPage(contest_file, contest[1], contest_page));
	CALL(bufMgr->unPinPage(contest_file, contest[1], false));
	CALL(bufMgr->disposePage(contest_file, contest[1]));
	FAIL(status = bufMgr->readPage(contest_file, contest[1], contest_page));
	Error::print(status);
	//CALL(bufMgr->allocPage(contest_file, contest[0], contest_page));
	
	//Mass update flushfile
	for(i = 0; i < num; i++)
	{
		CALL(bufMgr->allocPage(contest_file, contest[i], contest_page));
		sprintf((char*)contest_page,"Mass Update %d", i);
	}
	for(i = 0; i <  num;  i++)
	{
		CALL(bufMgr->unPinPage(contest_file, contest[i], true));
	}
	CALL(bufMgr->flushFile(contest_file));
	for(i = 0; i < num; i++)
	{
		CALL(bufMgr->readPage(contest_file, contest[i], contest_page));
		sprintf((char*)&cmp, "Mass Update %d", i);
		ASSERT(memcmp(contest_page, &cmp, strlen((char*)&cmp)) == 0);
		CALL(bufMgr->unPinPage(contest_file, contest[i], false));
		CALL(bufMgr->disposePage(contest_file, contest[i]));
	}
	//Mass update boot
	for(i = 0; i < num; i++)
	{
		CALL(bufMgr->allocPage(contest_file, contest[i], contest_page));
		sprintf((char*)contest_page,"Mass Update boot %d", i);
	}
	for(i = 0; i <  num;  i++)
	{
		CALL(bufMgr->unPinPage(contest_file, contest[i], true));
	}
	for(i = 200; i < num*3; i++)
	{
		CALL(bufMgr->allocPage(contest_file, contest[i], contest_page));
		//clock algorithm should boot the previous pages and write them to disk
	}
	for(i = 200; i < num*3; i++)
	{
		CALL(bufMgr->unPinPage(contest_file, contest[i], false));
	}
	for(i = 0; i < num; i++)
	{
		CALL(bufMgr->readPage(contest_file, contest[i], contest_page));
		sprintf((char*)&cmp, "Mass Update boot %d", i);
		ASSERT(memcmp(contest_page, &cmp, strlen((char*)&cmp)) == 0);
		CALL(bufMgr->unPinPage(contest_file, contest[i], false));
		CALL(bufMgr->disposePage(contest_file, contest[i]));
	}

	
	CALL(db.closeFile(contest_file));
	CALL(db.destroyFile("contests"));
	/*End Mac's test cases*/


    CALL(db.closeFile(file1));
    CALL(db.closeFile(file2));
    CALL(db.closeFile(file3));
    CALL(db.closeFile(file4));

    CALL(db.destroyFile("test.1"));
    CALL(db.destroyFile("test.2"));
    CALL(db.destroyFile("test.3"));
    CALL(db.destroyFile("test.4"));

    delete bufMgr;

    cout << endl << "Passed all tests." << endl;

    return (1);
}
